<?php

namespace YouBaoLian\Common;

use Exception;
use YouBaoLian\Config\Config;

class Encrypt
{

    /**
     * @var Config $config ;
     */
    private $config;

    private $encryptParams = [];

    private $decryptParams = [];

    private static $RSA_ENCRYPT_BLOCK_SIZE = 234;

    private static $RSA_DECRYPT_BLOCK_SIZE = 256;

    /**
     * @param Config $config
     */
    public function __construct(Config $config)
    {
        $this->config = $config;
    }

    /**
     * @return array
     */
    public function getEncryptParams(): array
    {
        return $this->encryptParams;
    }

    /**
     * @param array $encryptParams
     */
    public function setEncryptParams(array $encryptParams): void
    {
        $this->encryptParams = $encryptParams;
    }

    /**
     * @return array
     */
    public function getDecryptParams(): array
    {
        return $this->decryptParams;
    }

    /**
     * @param array $decryptParams
     */
    public function setDecryptParams(array $decryptParams): void
    {
        $this->decryptParams = $decryptParams;
    }

    /**
     * biz加密
     * @return void
     * @throws Exception
     */
    public function bizEncrypt()
    {
        if (!$this->encryptParams['bizContent']) {
            throw new Exception('参数中bizContent内容不能为空');
        }
        if (!$this->config->getEncryptKey()) {
            throw new Exception('优保联公钥不能为空');
        }

        $this->encryptParams['bizContent'] = $this->encrypt($this->encryptParams['bizContent']);

    }

    /**
     * biz解密
     * @return void
     * @throws Exception
     */
    public function bizDecrypt()
    {
        if (!$this->decryptParams['bizContent']) {
            throw new Exception('参数中bizContent内容不能为空');
        }
        if (!$this->config->getPrivateKey()) {
            throw new Exception('商户私钥不能为空');
        }

        $this->decryptParams['bizContent'] = $this->decrypt($this->decryptParams['bizContent']);
    }

    /**
     * 签名
     * @throws Exception
     * @author xis
     */
    public function sign()
    {
        if (!$this->encryptParams) {
            return '';
        }
        $this->encryptParams = array_filter($this->encryptParams);

        $this->encryptParams['sign'] = $this->signing($this->toSignString($this->encryptParams));
    }

    /**
     *验签
     * @return void
     * @throws Exception
     */
    public function verify(): bool
    {
        if (!$this->decryptParams) {
            return false;
        }

        $data = $this->toSignString($this->decryptParams);

        $sign = $this->decryptParams['sign'];

        if ($this->config->getEncryptKey()) {
            $pubKey = $this->config->getEncryptKey();
            $pubKey = "-----BEGIN PUBLIC KEY-----\n" .
                wordwrap($pubKey, 64, "\n", true) .
                "\n-----END PUBLIC KEY-----";

        } else {
            throw new Exception('请配置优保联公钥');
        }
        $result = openssl_verify($data, base64_decode($sign), $pubKey, OPENSSL_ALGO_SHA256);

        if (!$result) {
            throw new Exception('验签失败,请检查数据来源');
        }

        return $result === 1;
    }


    /**
     * sign生成
     * @param $string
     * @return string
     * @throws Exception
     */
    private function signing($string): string
    {
        if ($this->config->getPrivateKey()) {
            $priKey = $this->config->getPrivateKey();
            $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
                wordwrap($priKey, 64, "\n", true) .
                "\n-----END RSA PRIVATE KEY-----";
        } else {
            throw new Exception('请配置商户秘钥');
        }

        //私钥加密
        openssl_sign($string, $sign, $res, OPENSSL_ALGO_SHA256);

        return base64_encode($sign);
    }

    /**
     * 报文加密
     * @param $string
     * @return string
     * @throws Exception
     */
    private function encrypt($string): string
    {

        $pubKey = $this->config->getEncryptKey();
        $key = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($pubKey, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        //公钥加密
        $result = '';

        foreach (str_split($string, self::$RSA_ENCRYPT_BLOCK_SIZE) as $block) {

            openssl_public_encrypt($block, $encrypted, $key);

            $result .= $encrypted;
        }

        $result = base64_encode($result);

        if (!$result) {
            throw new Exception('encrypt error');
        }

        return $result;
    }

    /**
     * 报文解密
     * @param $string
     * @return string
     * @throws Exception
     */
    private function decrypt($string): array
    {
        $pri = $this->config->getPrivateKey();
        $priKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($pri, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        $string = base64_decode($string);
        $result = '';

        $data = str_split($string, self::$RSA_DECRYPT_BLOCK_SIZE);

        foreach ($data as $block) {
            //私钥解密
            openssl_private_decrypt($block, $encrypted, $priKey);
            if ($encrypted) {
                $result .= $encrypted;
            } else {
                break;
            }

        }

        if (!$result) {
            return [];
        } elseif (json_decode($result)) {
            return json_decode($result, true);
        } else {
            return [];
        }
    }

    /**
     * 待签名字符串
     * @author xis
     */
    private function toSignString($params): string
    {
        ksort($params);
        unset($params['sign']);

        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if ("@" != substr($v, 0, 1)) {

                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . "$v";
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . "$v";
                }
                $i++;
            }
        }
        unset ($k, $v);
        return $stringToBeSigned;
    }
}
